﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.SharpZipLib.Zip;
using System.Xml;
using System.IO;

namespace BAASearchLib
{
    public class PptxToText
    {
        /// <summary>
        ///  gives "p"
        /// </summary>
        private const string ContentTypeNamespace =
            @"http://schemas.openxmlformats.org/package/2006/content-types";

        /// <summary>
        ///  gives "a"
        /// </summary>
        private const string WordprocessingMlNamespace =
            @"http://schemas.openxmlformats.org/presentationml/2006/main";

        private const string BodyXPath = "/p:sld/p:cSld";

        private string pptxFile = "";

        private List<string> pptxFileLocations = new List<string>();


        public PptxToText(string fileName)
        {
            pptxFile = fileName;
        }

        #region ExtractText()
        /// <summary>
        /// Extracts text from the Pptx file.
        /// </summary>
        /// <returns>Extracted text.</returns>
        public string ExtractText()
        {
            if (string.IsNullOrEmpty(pptxFile))
                throw new Exception("Input file not specified.");

            // Usually it is "/ppt/slides/slideX.xml"

            FindDocumentXmlLocations();

            if (pptxFileLocations.Count == 0)
                throw new Exception("It is not a valid Pptx file.");

            return ReadDocumentXml();
        }
        #endregion

        #region FindDocumentXmlLocations()
        /// <summary>
        /// Gets location of the "slideX.xml" zip entries.
        /// </summary>
        /// <returns>Location of the "slideX.xml".</returns>
        private void FindDocumentXmlLocations()
        {
            ZipFile zip = new ZipFile(pptxFile);
            foreach (ZipEntry entry in zip)
            {
                string n = entry.Name;
                if (n.StartsWith("ppt/slides/slide") && n.EndsWith(".xml"))
                {
                    pptxFileLocations.Add(n.TrimStart(new char[] { '/' }));
                }
            }
            zip.Close();
        }
        #endregion

        #region ReadDocumentXml()
        /// <summary>
        /// Reads "slideX.xml" zip entries.
        /// </summary>
        /// <returns>Text containing in the pptx.</returns>
        private string ReadDocumentXml()
        {
            StringBuilder sb = new StringBuilder();

            ZipFile zip = new ZipFile(pptxFile);
            foreach (ZipEntry entry in zip)
            {
                if (pptxFileLocations.Contains(entry.Name))
                {
                    Stream documentXml = zip.GetInputStream(entry);

                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.PreserveWhitespace = true;
                    xmlDoc.Load(documentXml);
                    documentXml.Close();

                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
                    nsmgr.AddNamespace("p", WordprocessingMlNamespace);

                    XmlNode node = xmlDoc.DocumentElement.SelectSingleNode(BodyXPath, nsmgr);

                    if (node == null)
                        return string.Empty;

                    sb.Append(ReadNode(node));

                    //break;
                }
            }
            zip.Close();
            return sb.ToString();
        }
        #endregion

        #region ReadNode()
        /// <summary>
        /// Reads content of the node and its nested childs.
        /// </summary>
        /// <param name="node">XmlNode.</param>
        /// <returns>Text containing in the node.</returns>
        private string ReadNode(XmlNode node)
        {
            if (node == null || node.NodeType != XmlNodeType.Element)
                return string.Empty;

            StringBuilder sb = new StringBuilder();
            foreach (XmlNode child in node.ChildNodes)
            {
                if (child.NodeType != XmlNodeType.Element) continue;

                switch (child.LocalName)
                {
                    case "t":                           // Text
                        sb.Append(child.InnerText.Trim());
                        //Debug.WriteLine("APPEND SPACE");

                        //string space = ((XmlElement)child).GetAttribute("xml:space");
                        //if (!string.IsNullOrEmpty(space) && space == "preserve")
                        if ((sb.Length > 0 && sb[sb.Length - 1] != ' '))
                            sb.Append(' ');
                        break;

                    case "cr":                          // Carriage return
                    case "br":                          // Page break
                        sb.Append(Environment.NewLine);
                        break;

                    case "tab":                         // Tab
                        sb.Append("\t");
                        break;

                    case "p":                           // Paragraph
                        sb.Append(ReadNode(child));
                        sb.Append(Environment.NewLine);
                        sb.Append(Environment.NewLine);
                        break;
                    //case "rPr":                           // space?
                    //    sb.Append(" ");
                    //    break;
                    default:
                        sb.Append(ReadNode(child));
                        break;
                }
            }
            return sb.ToString();
        }
        #endregion

        #region GetAuthor() // made in 4 sec :)
        /// <summary>
        /// Gets the Author of the Pptx
        /// </summary>
        /// <returns>Author of the file</returns>
        public string GetAuthor()
        {
            ZipFile zip = new ZipFile(pptxFile);
            string xmlns = @"http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
            string BodyXPath = "/cp:coreProperties";
            string res = "";

            foreach (ZipEntry entry in zip)
            {
                // Find "core.xml" zip entry
                if (string.Compare(entry.Name, "docProps/core.xml", true) == 0)
                {
                    Stream contentTypes = zip.GetInputStream(entry);

                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.PreserveWhitespace = true;
                    xmlDoc.Load(contentTypes);
                    contentTypes.Close();

                    //Create an XmlNamespaceManager for resolving namespaces

                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
                    nsmgr.AddNamespace("cp", xmlns);

                    // Create a Node from the xml (?)

                    XmlNode node = xmlDoc.DocumentElement.SelectSingleNode(BodyXPath, nsmgr);

                    //loop trough the Node's childs
                    foreach (XmlNode child in node.ChildNodes)
                    {
                        if (child.NodeType != XmlNodeType.Element) continue;

                        switch (child.LocalName)
                        {
                            case "creator":                     // Author
                                res = child.InnerText;
                                break;
                        }
                    }
                    return res;
                }
            }
            zip.Close();
            return res;
        }
        #endregion
    }
}
